package mutation;


import java.lang.reflect.Array;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.ArrayList;
import ga.*;
import operations.*;
import util.DebugConfig;
import util.GAConfig;
import util.Log;
import util.Randomness;
import mdr.RecommendGenerator;
import model.*;
import instrumentation.Data;
import instrumentation.Value;
import instrumentation.Condition;
import instrumentation.ConditionSwitch;
import instrumentation.Constant;
import instrumentation.Virtualinvoke;
import instrumentation.ConstantString;
public class MDRChangeInputValue extends MutationStrategy {
	@Override
	public Individual mutate(Individual individual) {
		if(DebugConfig.ChangeInputValueClass){
			System.out.println("------- Recommended Mutation Change Value Strategy ---------------"); 
			System.out.println("--------------------individual1----------------------");
			//System.out.println(individual.toString()+ " size:"+ individual.getSize());
			System.out.println("\t size: "+ individual.getSize());
			System.out.println("-----------------------------------------------------");
		}

		// select random target to assign the mutations of the non-covered bracnhes.

			RecommendGenerator recommendGenerator=this.gaInfo.getRecommendGenerator();
			Integer trgtBR=-1;
			if(GAConfig.TargetHitBranches)
				trgtBR=MutationFactory.getHitTargetToCover(this.gaInfo);
			else
				trgtBR=MutationFactory.getTargetToCover(this.gaInfo);
			if(trgtBR==-1) return individual;
	//		for(trgtBR=1;trgtBR<20;trgtBR+=1){
		//	trgtBR=1;
				Condition c=recommendGenerator.getCondition(trgtBR);
//				System.out.println("MDRChangeInputValue trgtBR: "+ trgtBR+ " " +recommendGenerator.getBranchWithData(trgtBR));

				List<StatementKind>  BRRelatedStatementList=recommendGenerator.getRecommendMethodsBranch(trgtBR);
//				System.out.println("BRRelatedStatementList trgtBR: [ " + trgtBR + " ] " +BRRelatedStatementList);
	
				if(BRRelatedStatementList.size()==0)
					return individual;
	
				Map<Integer, Value> predicates=recommendGenerator.getBranchWithData(trgtBR);
				{

//					if(predicates.get(1)instanceof Constant &&predicates.get(2)instanceof Constant)
//						return individual;
					Value v1=predicates.get(1);
					Value v2=predicates.get(2);
					Data d1=null;
					if(v1 instanceof ConstantString){
						predicates.put(1, v2);
						predicates.put(2, v1);
						 v1=predicates.get(1);
						 v2=predicates.get(2);

					}
						if(v2 instanceof Data&& !(v1 instanceof Virtualinvoke)){
							d1=(Data)v1;
							Data d2=null;
							d2=(Data)v2;
							if(!(d1.isField()||d1.isParam())&&d2!=null)
								if(d2.isField()||d2.isParam()){
									predicates.put(1, v2);
									predicates.put(2, v1);
								}
						}
				}
				List<Value> v1_v2=MutationFactory.get_V1_and_V2(predicates);
//				System.out.println("v1: " +v1_v2.get(0));
//				System.out.println("v2: " +v1_v2.get(1));
				Value v1=v1_v2.get(0);
				//if(!(((Data)v1).isField()|| ((Data)v1).isParam())) return individual;

//			}
		Individual mutatedIndividual=individual.clone();
		
		// Change Initial Constructions
		try {
			List<Operation> cons = mutatedIndividual.getInitialConstructors();
			for (int i = 0; i < cons.size(); i++) {
				if (!getMutationFlag(BRRelatedStatementList.size()))
					continue;
				Operation mutateOp=null;
				Operation op = cons.get(i);
//				 System.out.println(i+ " ChangeInputValueClass: "+ op.toString());
				if (op instanceof CreateObject) {
					 mutateOp = mutantConstructoParams((CreateObject) op, mutatedIndividual,v1_v2,trgtBR,c, BRRelatedStatementList);

				} else if (op instanceof AssignConstant) {
					 mutateOp = mutantAssignConstant((AssignConstant) op, mutatedIndividual);
					}
				if(mutateOp==null) continue;
				cons.set(i, mutateOp);

			}

			List<Operation> ops = mutatedIndividual.getStatements();
			for (int i = 0; i < ops.size(); i++) {
				Operation op = ops.get(i);
				Operation mutateOp=null;
				if (!getMutationFlag(BRRelatedStatementList.size()))
					continue;
				if(ops.get(i) instanceof CreateArray)
					 mutateOp = mutantArray((CreateArray) op,
								mutatedIndividual);

				if (ops.get(i) instanceof InvokeMethod) {				
					 mutateOp = mutantMethodParams((InvokeMethod) op,mutatedIndividual,trgtBR,c,v1_v2, BRRelatedStatementList);
				} else if (ops.get(i) instanceof CreateObject) {
					 mutateOp = mutantConstructoParams((CreateObject) op, mutatedIndividual,v1_v2,trgtBR,c, BRRelatedStatementList);

				} else if (ops.get(i) instanceof AssignConstant) {
					 mutateOp = mutantAssignConstant((AssignConstant) op, mutatedIndividual);
				}
				if(mutateOp==null) continue;
				ops.set(i, mutateOp);

			}
		} catch (Throwable e){
			System.err.println("MDRChangeInputValue:" + e .getMessage());
		}	
		if(DebugConfig.ChangeInputValueClass){
			Log.log("--------------------individual1----------------------");
			Log.log(individual.toString()+ " size:"+ individual.getSize());
			Log.log("--------------------mutatedIndividual----------------------");
			Log.log(mutatedIndividual.toString()+ " size:"+ mutatedIndividual.getSize());
		}

		return mutatedIndividual;
	}
	private Operation mutantArray(CreateArray op,Individual individual) throws Throwable {
		TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
		 CreateArray array=((CreateArray)op);
		   Object theArray = Array.newInstance(array.getElementType(), array.getLength());
		   List<Object> inputList=array.getInputVariables();
		    for (int i = 0; i < array.getLength(); i++)
		    	if(Filter.isPrimitiveOrStringType(array.getElementType()))
		    		inputList.set(i, ValuesGenerator.modifyPrimitiveValue(array.getElementType(),inputList.get(i)));
		    	else
		    		inputList.set(i, Operation.generateReference(array.getElementType(),TestCluster));

		return op;
	}
			private Operation mutantMethodParams(InvokeMethod op,Individual individual,int trgtBR,Condition c,List<Value> v1_v2,List<StatementKind> BRRelatedStatementList) throws Throwable {
//				
//				System.out.println("mdr.mutantMethodParams"+ op.toString());
//				if(op.toString().contains("nanoxml.StdXMLReader.stringReader"))
//					System.out.println("mdr.mutantMethodParams"+ op.toString());
				try {
				if(!(op instanceof operations.InvokeMethod)) return op;

//				System.out.println("mdr.mutantMethodParams******** "+ op.toString());
//				System.out.println("mdr.mutantMethodParams v1_v2"+ v1_v2);
//				System.out.println("mdr.mutantMethodParams c"+ c.toString());

				StatementKind m = op.getMethod();
			//	Class<?> clz=recClassToTest.get(Randomness.nextRandomInt(recClassToTest.size()));
				Class<?> clzMethod=((InvokeMethod)op).getMethod().getClass();
				if(!BRRelatedStatementList.contains(((InvokeMethod)op).getMethod())) return op;

				TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
				Map<String, String[]> refFactory = TestCluster.getRefFactory();
				List<Class<?>>replacementObjectParam= op.getReplacementObjectParam();
				
				List<Class<?>> paramsTpe = m.getInputTypes();
				List<Object> oldValues=op.getValues();
				List<Object> newValues=new LinkedList<Object>();

				//Change return reference
				String _return = (((MethodDeclared) m).isVoid()) ? null : Operation.getReference(((MethodDeclared) m).getMethod().getReturnType().getName(), TestCluster.getRefFactory());
				if(_return==null&&!((MethodDeclared) m).isVoid()){
					Class<?> rType=Operation.generateReference(((MethodDeclared) m).getMethod().getReturnType(), TestCluster);
					if(!rType.getName().contains("java."))
					if(!rType.getName().startsWith("sun"))
					if(!rType.getName().contains("$")) 
					if(!Filter.isPrimitiveOrStringType(rType)){
									_return=Operation.getReference(rType.getName(), refFactory);//clz2.toString();	
						}
				}
				op.setReturn(_return);
				//change parameters
					String pValue2=null;
//					List<Value> v1_v2=MutationFactory.get_V1_and_V2(predicates);
					Object pType1=MutationFactory.getTpeValue(v1_v2.get(0));
					Object pType2=MutationFactory.getTpeValue(v1_v2.get(1));
					Value v1=v1_v2.get(0);
					Value v2=v1_v2.get(1);
					if(v2 instanceof instrumentation.Constant){
							pValue2=v2.toString();
						}else 
							if(v2 instanceof instrumentation.ConstantString)
								pValue2=v2.toString();
				
				Class<?> pValueTyp=null;//Filter.getPrimitiveTypeOrString(c.getType().toString());
				if(pValueTyp==null&&pType1!=null)
					pValueTyp=Filter.getPrimitiveTypeOrString(pType1.toString());
				if(pValueTyp==null&&pType1!=null)//if type is Object class
					pValueTyp=Filter.getObjectType(pType1.toString());
				
				Object pValue=ValuesGenerator.mapValueToObject(pValueTyp, pValue2);
				Class<?> conditionTpe=Filter.getPrimitiveTypeOrString(c.getType().toString());

				if(c.getType().equals("Character")||pValue==null){
				//	pValueTyp=conditionTpe;
					pValue=ValuesGenerator.mapValueToObject(conditionTpe, pValue2);
				}
				//Class<?>	pValueTyp2=Filter.getPrimitiveTypeOrString(pType2.toString());
				String fullMethodTarget=this.gaInfo.getRecommendGenerator().getTrgtMethod(trgtBR).toString();
				
				int start_index=0, end_index=paramsTpe.size()-1;
//				if(op.getMethod().toString().equals(fullMethodTarget))
//					if(((Data)v1).isParam()&&((Data)v1).getIndxParam()<=end_index){
//						if(((Data)v1).getIndxParam()>=0){
//							start_index=((Data)v1).getIndxParam();
//							end_index=((Data)v1).getIndxParam();
//						}
//					}
				for(int i = start_index; i <=end_index ; i++) {
					Class<?> t=paramsTpe.get(i);
					replacementObjectParam.set(i,t);
					if (Filter.isPrimitiveOrStringType(t)){
						Object tempValue=oldValues.get(i);

						if(pValueTyp==null||pValueTyp.equals(t)||(pValueTyp.equals(char.class)&&t.equals(String.class))){
							oldValues.set(i,ValuesGenerator.modifyPrimitiveValue(t,oldValues.get(i)));
//							continue;
						}
//						if(!(((Data)v1).isField()|| ((Data)v1).isParam())) continue;
						if(!GAConfig.Seeding||pValueTyp==null)continue;
						if(!pValueTyp.equals(t)&&!pValueTyp.equals(char.class))continue;
						if(!t.equals(String.class)) continue;
						if(ValuesGenerator.getBoolean(.8f)){
								if(pValue==null && t.equals(String.class)&&ValuesGenerator.getBoolean(.5f))
									oldValues.set(i,null);
								else
									oldValues.set(i,ValuesGenerator.modifyRecommendedValue(tempValue,pValue,t));
						}
					}
						else{
							if(Filter.isObjectType(t)){
								t=Operation.generateMDRReference(t, BRRelatedStatementList, TestCluster);
								if(t==null) // relatedStatemt dont contains constructors;
									t=Operation.generateReference(paramsTpe.get(i),TestCluster);
								if (Filter.isPrimitiveOrStringType(t))
									oldValues.set(i,ValuesGenerator.generatePrimitiveValue(t));
								else{
									if(ValuesGenerator.getBoolean(.8f)&& pValue==null && pType1!=null&& GAConfig.Seeding)
										oldValues.set(i,null);
									else
										oldValues.set(i,Operation.getReference(t.getName(), refFactory));
								}
							}
							else{
								t=Operation.generateReference(t, TestCluster);
								if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
									oldValues.set(i,null);
								else
									oldValues.set(i,Operation.getReference(t.getName(), refFactory));
							}						
							}
					replacementObjectParam.set(i,t);

				}
					newValues.addAll(oldValues);
					op.setValues(newValues);
					op.addInfo(new OperationResult());

				
				} catch (Throwable e){
					System.err.println("MDRChangeInputValue.mutantMethodParams:" + e.getMessage());
				}	
//				if(op.toString().contains("nanoxml.StdXMLReader.stringReader"))
//					System.out.println("mdr.mutantMethodParams"+ op.toString());

				return op;
			}
			private Operation mutantConstructoParams(CreateObject op,Individual individual,List<Value> v1_v2,int trgtBR,Condition c, List<StatementKind> BRRelatedStatementList) throws Throwable {
				try{
				TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
				Map<String, String[]> refFactory = TestCluster.getRefFactory();
				StatementKind cons = op.getConstructor();
				
				if(!BRRelatedStatementList.contains(((CreateObject)op).getConstructor())) return op;

				List<Class<?>> paramsTpe = cons.getInputTypes();
				List<Object> oldValues=op.getValues();
				List<Object> newValues=new LinkedList<Object>();
// detect parameters or field that is used in the targtBranch
				RecommendGenerator recommendGenerator=this.gaInfo.getRecommendGenerator();
				Map<Integer, Value> predicates=recommendGenerator.getBranchWithData(trgtBR);
				String pValue2=null;
//				List<Value> v1_v2=MutationFactory.get_V1_and_V2(predicates);
				Object pType1=MutationFactory.getTpeValue(v1_v2.get(0));
				Object pType2=MutationFactory.getTpeValue(v1_v2.get(1));
				Value v1=v1_v2.get(0);
				Value v2=v1_v2.get(1);
				if(v2 instanceof instrumentation.Constant){
						pValue2=v2.toString();
					}else 
						if(v2 instanceof instrumentation.ConstantString)
							pValue2=v2.toString();
			
			Class<?> pValueTyp=null;//Filter.getPrimitiveTypeOrString(c.getType().toString());
			if(pValueTyp==null&&pType1!=null)
				pValueTyp=Filter.getPrimitiveTypeOrString(pType1.toString());
			if(pValueTyp==null&&pType1!=null)//if type is Object class
				pValueTyp=Filter.getObjectType(pType1.toString());
			
			Object pValue=ValuesGenerator.mapValueToObject(pValueTyp, pValue2);
			Class<?> conditionTpe=Filter.getPrimitiveTypeOrString(c.getType().toString());

			if(c.getType().toString().equals("Character")||pValue==null){
				//pValueTyp=conditionTpe;
				pValue=ValuesGenerator.mapValueToObject(conditionTpe, pValue2);
			}
			//Class<?>	pValueTyp2=Filter.getPrimitiveTypeOrString(pType2.toString());
			String fullMethodTarget=this.gaInfo.getRecommendGenerator().getTrgtMethod(trgtBR).toString();
			
			int start_index=0, end_index=paramsTpe.size()-1;
//			if(op.toString().equals(fullMethodTarget))
//				if(((Data)v1).isParam()&&((Data)v1).getIndxParam()<=end_index){
//					if(((Data)v1).getIndxParam()>=0){
//						start_index=((Data)v1).getIndxParam();
//						end_index=((Data)v1).getIndxParam();
//					}
//				}
			for(int i = start_index; i <=end_index ; i++) {
				Class<?> t=paramsTpe.get(i);
				if (Filter.isPrimitiveOrStringType(t)){
					Object tempValue=oldValues.get(i);
					if(pValueTyp==null||pValueTyp.equals(t)){
						oldValues.set(i,ValuesGenerator.modifyPrimitiveValue(t,oldValues.get(i)));
//						continue;
					}
//					if(!(((Data)v1).isField()|| ((Data)v1).isParam())) continue;
					if(!GAConfig.Seeding||pValueTyp==null)continue;
					if(pValueTyp!=null &&!pValueTyp.equals(t)&&!pValueTyp.equals(char.class))continue;
					if(!t.equals(String.class)) continue;
					if(ValuesGenerator.getBoolean(.8f)){
							if(pValue==null && t.equals(String.class)&&ValuesGenerator.getBoolean(.5f))
								oldValues.set(i,null);
							else
								oldValues.set(i,ValuesGenerator.modifyRecommendedValue(tempValue,pValue,t));
					}
				}
					else{
						if(Filter.isObjectType(t)){
							t=Operation.generateMDRReference(t, BRRelatedStatementList, TestCluster);
							if(t==null) // relatedStatemt dont contains constructors;
								t=Operation.generateReference(paramsTpe.get(i),TestCluster);
							if (Filter.isPrimitiveOrStringType(t))
								oldValues.set(i,ValuesGenerator.generatePrimitiveValue(t));
							else{
								if(ValuesGenerator.getBoolean(.8f)&& pValue==null && pType1!=null&& GAConfig.Seeding)
									oldValues.set(i,null);
								else
									oldValues.set(i,Operation.getReference(t.getName(), refFactory));
							}
						}
						else{
							t=Operation.generateReference(t, TestCluster);
							if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
								oldValues.set(i,null);
							else
								oldValues.set(i,Operation.getReference(t.getName(), refFactory));
						}						
						}

			}
				newValues.addAll(oldValues);
				op.setValues(newValues);
				op.addInfo(new OperationResult());
			} catch (Throwable e){
				System.err.println("MDRChangeInputValue.mutantConstructoParams:" + e.getMessage());
			}	
				return op;
			}
			private Operation mutantAssignConstant(AssignConstant op,Individual individual) throws Throwable {
				TestClusterBuilder TestCluster=this.gaInfo.getTestCluster();
				Map<String, CUT>  cluster= TestCluster.getCluster();
				String cName= ((AssignConstant) op).getDeclaringClass().getName();
				CUT cut =cluster.get(cName) ;
				op = AssignConstant.generate(cut,TestCluster);
				return op;
			}
	@Override
	public boolean getMutationFlag(int size) {
		if(ValuesGenerator.getBoolean(1.0f/size))
			return true;
		return false;

	}
}
